﻿namespace RPCenter.WindowsAPI
{
    using Microsoft.VisualBasic.CompilerServices; 
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Diagnostics;
    using System.Runtime.InteropServices;
    using System.ServiceProcess;

    public class NativeService : IComparable<NativeService>
    {
        private string _AccountName;
        private string _DisplayName;
        private string _Name;
        private int _ProcessID;
        private ServiceState _State;

        public NativeService(string ServiceName, string FriendlyName, int ID, ServiceState CurrentState)
        {
            this._Name = string.Empty;
            this._DisplayName = string.Empty;
            this._ProcessID = 0;
            this._AccountName = string.Empty;
            this._Name = ServiceName;
            this._DisplayName = FriendlyName;
            this._ProcessID = ID;
            this._State = CurrentState;
        }

        public NativeService(string ServiceName, string FriendlyName, int ID, ServiceState CurrentState, string Account)
        {
            this._Name = string.Empty;
            this._DisplayName = string.Empty;
            this._ProcessID = 0;
            this._AccountName = string.Empty;
            this._Name = ServiceName;
            this._DisplayName = FriendlyName;
            this._ProcessID = ID;
            this._State = CurrentState;
            this._AccountName = Account;
        }

        public int CompareTo(NativeService other)
        {
            return string.Compare(this.DisplayName, other.DisplayName);
        }

        private static IntPtr CreateService(string MachineName, string ExecutablePath, string ServiceName, string DisplayName, ServiceStartupType StartType, string LogonUsername, string LogonPassword)
        {
            IntPtr ptr;
            if (!ExecutablePath.StartsWith("\""))
            {
                ExecutablePath = "\"" + ExecutablePath;
            }
            if (!ExecutablePath.EndsWith("\""))
            {
                ExecutablePath = ExecutablePath + "\"";
            }
            IntPtr zero = IntPtr.Zero;
            try
            {
                IntPtr ptr4 = IntPtr.Zero;
                zero = GetServiceControlManagerHandle(MachineName, 2);
                ptr = ApiDefinitions.CreateService(zero, ServiceName, DisplayName, 0xf01ff, 0x10, (uint) StartType, 1, ExecutablePath, null, ptr4, null, LogonUsername, LogonPassword);
            }
            finally
            {
                if (!(zero == IntPtr.Zero))
                {
                    ApiDefinitions.CloseServiceHandle(zero);
                }
            }
            return ptr;
        }

        public static void DeleteService(string ServiceName)
        {
            DeleteService(ServiceName, string.Empty);
        }

        public static void DeleteService(string ServiceName, string MachineName)
        {
            IntPtr zero = IntPtr.Zero;
            IntPtr hService = IntPtr.Zero;
            try
            {
                zero = GetServiceControlManagerHandle(MachineName, 4);
                hService = ApiDefinitions.OpenService(zero, ServiceName, 0x10000);
                if (hService == IntPtr.Zero)
                {
                    throw new ApplicationException("Unable to access service " + ServiceName + ". The last error reported was: " + new Win32Exception().Message);
                }
                if (!ApiDefinitions.DeleteService(hService))
                {
                    throw new ApplicationException("Unable to delete service " + ServiceName + ". The last error reported was: " + new Win32Exception().Message);
                }
            }
            finally
            {
                if (!(hService == IntPtr.Zero))
                {
                    ApiDefinitions.CloseServiceHandle(hService);
                }
                if (!(zero == IntPtr.Zero))
                {
                    ApiDefinitions.CloseServiceHandle(zero);
                }
            }
        }

        private static void GetDependentServices(string ServiceName, string MachineName, Queue<string> CurrentList)
        {
            if (!CurrentList.Contains(ServiceName))
            {
                using (ServiceController controller = new ServiceController(ServiceName, MachineName))
                {
                    if (controller.Status == ServiceControllerStatus.Running)
                    {
                        CurrentList.Enqueue(ServiceName);
                        foreach (ServiceController controller2 in controller.DependentServices)
                        {
                            GetDependentServices(controller2.ServiceName, MachineName, CurrentList);
                        }
                    }
                }
            }
        }

        public static IntPtr GetServiceControlManagerHandle(string MachineName, uint Access)
        {
            IntPtr ptr2 = ApiDefinitions.OpenSCManager(MachineName, null, Access);
            if (ptr2 == IntPtr.Zero)
            {
                throw new ApplicationException("Unable to connect to service manager on " + MachineName + ". The last error that was reported was: " + new Win32Exception().Message);
            }
            return ptr2;
        }

        public static List<NativeService> GetServices(ServiceStateToEnumerate State)
        {
            return GetServices(State, string.Empty);
        }

        public static List<NativeService> GetServices(ServiceStateToEnumerate State, string MachineName)
        {
            IntPtr ptr3 = IntPtr.Zero;
            IntPtr serviceControlManagerHandle = GetServiceControlManagerHandle(MachineName, 4);
            uint pcbBytesNeeded = 0;
            uint lpServicesReturned = 0;
            ApiDefinitions.EnumServicesStatusEx(serviceControlManagerHandle, 0, 0x30, (uint) State, IntPtr.Zero, 0, out pcbBytesNeeded, out lpServicesReturned, ptr3, null);
            IntPtr lpServices = Marshal.AllocHGlobal((int) pcbBytesNeeded);
            List<NativeService> list2 = new List<NativeService>();
            try
            {
                uint num4 = 0;
                uint num3 = 0;
                ApiDefinitions.EnumServicesStatusEx(serviceControlManagerHandle, 0, 0x30, (uint) State, lpServices, pcbBytesNeeded, out num3, out num4, ptr3, null);
                long num2 = lpServices.ToInt64();
                int num5 = Marshal.SizeOf(typeof(ApiDefinitions.ENUM_SERVICE_STATUS_PROCESS));
                int num8 = (int) (num4 - 1L);
                for (int i = 0; i <= num8; i++)
                {
                    IntPtr ptr = new IntPtr(num2);
                    ApiDefinitions.ENUM_SERVICE_STATUS_PROCESS enum_service_status_process = (ApiDefinitions.ENUM_SERVICE_STATUS_PROCESS) Marshal.PtrToStructure(ptr, typeof(ApiDefinitions.ENUM_SERVICE_STATUS_PROCESS));
                    list2.Add(new NativeService(enum_service_status_process.lpServiceName, enum_service_status_process.lpDisplayName, (int) enum_service_status_process.ServiceStatusProcess.dwProcessId, (ServiceState) enum_service_status_process.ServiceStatusProcess.dwCurrentState));
                    num2 += num5;
                }
            }
            finally
            {
                if (!(lpServices == IntPtr.Zero))
                {
                    Marshal.FreeHGlobal(lpServices);
                }
                if (!(serviceControlManagerHandle == IntPtr.Zero))
                {
                    ApiDefinitions.CloseServiceHandle(serviceControlManagerHandle);
                }
            }
            list2.Sort();
            return list2;
        }

        public static List<NativeService> GetServices(ServiceStateToEnumerate State, string MachineName, string AccountName)
        {
            List<NativeService> list;
            List<NativeService> list2 = new List<NativeService>();
            IntPtr zero = IntPtr.Zero;
            try
            {
                foreach (NativeService service in GetServices(State, MachineName))
                {
                    if (zero == IntPtr.Zero)
                    {
                        zero = GetServiceControlManagerHandle(MachineName, 4);
                    }
                    IntPtr hService = IntPtr.Zero;
                    try
                    {
                        uint num;
                        IntPtr ptr4 = IntPtr.Zero;
                        hService = ApiDefinitions.OpenService(zero, service.Name, 1);
                        if (hService == IntPtr.Zero)
                        {
                            throw new ApplicationException("Unable to access service " + service.Name + " on computer " + MachineName + ". The last error reported was: " + new Win32Exception().Message);
                        }
                        ApiDefinitions.QUERY_SERVICE_CONFIG structure = new ApiDefinitions.QUERY_SERVICE_CONFIG();
                        ApiDefinitions.QueryServiceConfig(hService, ptr4, 0, out num);
                        IntPtr lpServiceConfig = Marshal.AllocHGlobal((int) num);
                        try
                        {
                            if (!ApiDefinitions.QueryServiceConfig(hService, lpServiceConfig, num, out num))
                            {
                                throw new ApplicationException("Unable to query service " + service.Name + " on computer " + MachineName + ". The last error reported was: " + new Win32Exception().Message);
                            }
                            Marshal.PtrToStructure(lpServiceConfig, structure);
                            if (string.IsNullOrEmpty(AccountName) || (string.Compare(structure.lpServiceStartName, AccountName, true) == 0))
                            {
                                list2.Add(new NativeService(service.Name, service.DisplayName, service.ProcessID, service.State, structure.lpServiceStartName));
                            }
                            continue;
                        }
                        finally
                        {
                            Marshal.FreeHGlobal(lpServiceConfig);
                        }
                    }
                    finally
                    {
                        if (!(hService == IntPtr.Zero))
                        {
                            ApiDefinitions.CloseServiceHandle(hService);
                        }
                    }
                }
                list = list2;
            }
            finally
            {
                if (!(zero == IntPtr.Zero))
                {
                    ApiDefinitions.CloseServiceHandle(zero);
                }
            }
            return list;
        }

        public static List<NativeService> GetServicesFromProcess(Process TargetProcess)
        {
            if (TargetProcess == null)
            {
                throw new ArgumentNullException("TargetProcess");
            }
            if (TargetProcess.Id == 0)
            {
                throw new ApplicationException("The process passed to GetServicesFromProcess does not have a valid process ID");
            }
            List<NativeService> list2 = new List<NativeService>();
            foreach (NativeService service in GetServices(ServiceStateToEnumerate.Active, string.Empty))
            {
                if (service.ProcessID == TargetProcess.Id)
                {
                    list2.Add(service);
                }
            }
            return list2;
        }

        private static ApiDefinitions.SERVICE_STATES GetServiceState(IntPtr ServiceHandle)
        {
            IntPtr ptr2 = IntPtr.Zero;
            uint pcbBytesNeeded = 0;
            ApiDefinitions.QueryServiceStatusEx(ServiceHandle, 0, ptr2, 0, out pcbBytesNeeded);
            IntPtr lpBuffer = Marshal.AllocHGlobal((int) pcbBytesNeeded);
            uint num2 = 0;
            if (!ApiDefinitions.QueryServiceStatusEx(ServiceHandle, 0, lpBuffer, pcbBytesNeeded, out num2))
            {
                throw new ApplicationException("Unable to query status of service. The last error reported was: " + new Win32Exception().Message);
            }
            ApiDefinitions.SERVICE_STATUS_PROCESS service_status_process = (ApiDefinitions.SERVICE_STATUS_PROCESS) Marshal.PtrToStructure(lpBuffer, typeof(ApiDefinitions.SERVICE_STATUS_PROCESS));
            if (!(lpBuffer == IntPtr.Zero))
            {
                Marshal.FreeHGlobal(lpBuffer);
            }
            return service_status_process.dwCurrentState;
        }

        public static void InstallAndStartService(string MachineName, string ExecutablePath, string ServiceName, string Displayname, ServiceStartupType StartType, ServiceLogonAccount LogonAccount)
        {
            string logonUsername = LogonAccount.ToString();
            if (LogonAccount != ServiceLogonAccount.LocalSystem)
            {
                logonUsername = @"NT Authority\" + logonUsername;
            }
            InstallAndStartService(MachineName, ExecutablePath, ServiceName, Displayname, StartType, logonUsername, string.Empty);
        }

        public static void InstallAndStartService(string MachineName, string ExecutablePath, string ServiceName, string Displayname, ServiceStartupType StartType, string LogonUsername, string LogonPassword)
        {
            IntPtr zero = IntPtr.Zero;
            try
            {
                zero = CreateService(MachineName, ExecutablePath, ServiceName, Displayname, StartType, LogonUsername, LogonPassword);
                if (zero == IntPtr.Zero)
                {
                    throw new ApplicationException("Unable to create service " + ServiceName + ". The last error reported was: " + new Win32Exception().Message);
                }
                if (!ApiDefinitions.StartService(zero, 0, IntPtr.Zero))
                {
                    throw new ApplicationException("Service " + ServiceName + " has been created but was unable to start. The last error reported was: " + new Win32Exception().Message);
                }
            }
            finally
            {
                if (!(zero == IntPtr.Zero))
                {
                    ApiDefinitions.CloseServiceHandle(zero);
                }
            }
        }

        public static void InstallService(string MachineName, string ExecutablePath, string ServiceName, string DisplayName, ServiceStartupType StartType, ServiceLogonAccount LogonAccount)
        {
            string logonUsername = LogonAccount.ToString();
            if (LogonAccount != ServiceLogonAccount.LocalSystem)
            {
                logonUsername = @"NT Authority\" + logonUsername;
            }
            InstallService(MachineName, ExecutablePath, ServiceName, DisplayName, StartType, logonUsername, string.Empty);
        }

        public static void InstallService(string MachineName, string ExecutablePath, string ServiceName, string DisplayName, ServiceStartupType StartType, string LogonUsername, string LogonPassword)
        {
            IntPtr zero = IntPtr.Zero;
            zero = CreateService(MachineName, ExecutablePath, ServiceName, DisplayName, StartType, LogonUsername, LogonPassword);
            if (zero == IntPtr.Zero)
            {
                throw new ApplicationException("Unable to create service. The last error reported was: " + new Win32Exception().Message);
            }
            ApiDefinitions.CloseServiceHandle(zero);
        }

        private static void RestartServiceAndDependentServices(string ServiceName, string MachineName)
        {
            if (string.IsNullOrEmpty(MachineName))
            {
                MachineName = Environment.MachineName;
            }
            using (ServiceController controller = new ServiceController(ServiceName, MachineName))
            {
                controller.Refresh();
                if (controller.Status == ServiceControllerStatus.Running)
                {
                    Queue<string> currentList = new Queue<string>();
                    GetDependentServices(ServiceName, MachineName, currentList);
                    try
                    {
                        controller.Stop();
                        controller.Refresh();
                        controller.WaitForStatus(ServiceControllerStatus.Stopped, TimeSpan.FromSeconds(60.0));
                    }
                    catch (Exception exception1)
                    {
                        ProjectData.SetProjectError(exception1);
                        Exception exception = exception1;
                        throw new ApplicationException("Failed to stop service " + ServiceName + " due to the following error: " + exception.Message);
                    }
                    do
                    {
                        ServiceController controller2 = new ServiceController(currentList.Dequeue(), MachineName);
                        try
                        {
                            controller2.Start();
                            controller.Refresh();
                            controller2.WaitForStatus(ServiceControllerStatus.Running, TimeSpan.FromSeconds(60.0));
                        }
                        catch (Exception exception3)
                        {
                            ProjectData.SetProjectError(exception3);
                            Exception exception2 = exception3;
                            if (exception2.InnerException != null)
                            {
                                throw new ApplicationException("Successfully stopped service " + ServiceName + " but failed to restart due to the following error: " + exception2.InnerException.Message);
                            }
                            throw new ApplicationException("Successfully stopped service " + ServiceName + " but failed to restart due to the following error: " + exception2.Message);
                        }
                        finally
                        {
                            if (controller2 != null)
                            {
                                controller2.Dispose();
                            }
                        }
                    }
                    while (currentList.Count != 0);
                }
            }
        }

        public static void SetServiceCredentials(string ServiceName, string NewUsername, string NewPassword, bool RestartService, bool GrantLogonAsServiceRight, string MachineName)
        {
            IntPtr zero = IntPtr.Zero;
            IntPtr hService = IntPtr.Zero;
            if (GrantLogonAsServiceRight)
            {
                try
                {
                    NativeSecurity.AddUserLogonRight(NewUsername, "SeServiceLogonRight", MachineName);
                }
                catch (Exception exception1)
                {
                    ProjectData.SetProjectError(exception1);
                    Exception exception = exception1;
                    throw new ApplicationException("Error granting user Logon As A Service right: " + exception.Message);
                }
            }
            try
            {
                IntPtr ptr3 = IntPtr.Zero;
                zero = GetServiceControlManagerHandle(MachineName, 4);
                hService = ApiDefinitions.OpenService(zero, ServiceName, 0xf01ff);
                if (hService == IntPtr.Zero)
                {
                    throw new ApplicationException("Unable to access service " + ServiceName + ". The last error reported was: " + new Win32Exception().Message);
                }
                if (!ApiDefinitions.ChangeServiceConfig(hService, uint.MaxValue, uint.MaxValue, uint.MaxValue, null, null, ptr3, null, NewUsername, NewPassword, null))
                {
                    throw new ApplicationException("Unable to update credentials used to run service " + ServiceName + ". The last error reported was: " + new Win32Exception().Message);
                }
                if (RestartService)
                {
                    try
                    {
                        if (GetServiceState(hService) == ApiDefinitions.SERVICE_STATES.SERVICE_RUNNING)
                        {
                            RestartServiceAndDependentServices(ServiceName, MachineName);
                        }
                    }
                    catch (Exception exception3)
                    {
                        ProjectData.SetProjectError(exception3);
                        Exception exception2 = exception3;
                        throw new ApplicationException("Service credentials have been updated, but the following error was encountered whilst attempting to restart the service: " + exception2.Message);
                    }
                }
            }
            finally
            {
                if (!(hService == IntPtr.Zero))
                {
                    ApiDefinitions.CloseServiceHandle(hService);
                }
                if (!(zero == IntPtr.Zero))
                {
                    ApiDefinitions.CloseServiceHandle(zero);
                }
            }
        }

        public override string ToString()
        {
            return (this.DisplayName + "  -  " + this.State.ToString());
        }

        public string AccountName
        {
            get
            {
                return this._AccountName;
            }
        }

        public string DisplayName
        {
            get
            {
                return this._DisplayName;
            }
        }

        public string Name
        {
            get
            {
                return this._Name;
            }
        }

        public int ProcessID
        {
            get
            {
                return this._ProcessID;
            }
        }

        public ServiceState State
        {
            get
            {
                return this._State;
            }
        }

        public enum ServiceLogonAccount
        {
            LocalService = 2,
            LocalSystem = 1,
            NetworkService = 3
        }

        public enum ServiceStartupType : uint
        {
            Automatic = 2,
            Disabled = 4,
            Manual = 3
        }

        public enum ServiceState : uint
        {
            ContinuePending = 5,
            Paused = 7,
            PausePending = 6,
            Running = 4,
            StartPending = 2,
            Stopped = 1,
            StopPending = 3,
            Unknown = 0
        }

        public enum ServiceStateToEnumerate : uint
        {
            Active = 1,
            All = 3,
            Stopped = 2
        }
    }
}

